#!/usr/bin/env python3
"""
跨平台 Nuitka 打包脚本 - 液冷机通信控制软件
优化版本：正确处理现有的桌面文件和图标文件
"""

import os
import sys
import shutil
import platform
import subprocess
import json
from pathlib import Path
from datetime import datetime
from typing import List, Dict, Optional

# 尝试导入项目设置，如果失败则使用默认值
try:
    from global_settings import debug_print, APP_VERSION, APP_NAME
except ImportError:
    APP_VERSION = "1.4.0"
    
    def debug_print(msg: str) -> None:
        """默认调试输出函数"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"[{timestamp}] {msg}")


class NuitkaBuilder:
    """优化的 Nuitka 跨平台打包器 - 正确处理现有桌面文件"""
    def __init__(self):
        self.system = platform.system().lower()
        self.arch = platform.machine().lower()
        self.app_name = f"{APP_NAME}_v{APP_VERSION}"
        self.build_dir = "dist"
        self.temp_dir = "build_temp"
        self.nuitka_version = None
        
        # 平台特定配置 - 使用现有的 favicon.png
        self.platform_config = {
            "windows": {
                "exe_suffix": ".exe",
                "icon_option": "--windows-icon-from-ico=favicon.ico",
                "console_option": "--windows-disable-console",
                "company_name": "zynwh",
                "file_description": "TMS-Controller",
                "icon_file": "favicon.ico"
            },
            "darwin": {
                "exe_suffix": ".app",
                "icon_option": "--macos-app-icon=favicon.icns",
                "console_option": "--macos-disable-console",
                "company_name": "zynwh",
                "create_app_bundle": True,
                "icon_file": "favicon.icns"
            },
            "linux": {
                "exe_suffix": "",
                "icon_option": "--linux-icon=favicon.png",  # 使用现有的 favicon.png
                "console_option": "--disable-console",
                "company_name": "zynwh",
                "desktop_file": "tms-controller.desktop",  # 使用现有的桌面文件
                "install_icon_name": "tms-controller.png"  # 安装时的图标名称
            }
        }
        
        self.config = self.platform_config.get(self.system, self.platform_config["linux"])
        
    def clean_previous_builds(self) -> None:
        """清理之前的构建文件"""
        targets = [
            self.build_dir,
            self.temp_dir,
            f"{self.app_name}.dist",
            f"{self.app_name}.build",
            f"{self.app_name}.exe",
            f"{self.app_name}.app",
            "__pycache__",
        ]
        
        cleaned_count = 0
        for target in targets:
            try:
                if os.path.exists(target):
                    if os.path.isdir(target):
                        shutil.rmtree(target)
                        debug_print(f"已删除目录: {target}")
                    else:
                        os.remove(target)
                        debug_print(f"已删除文件: {target}")
                    cleaned_count += 1
            except Exception as e:
                debug_print(f"删除 {target} 时出错: {e}")
        
        if cleaned_count > 0:
            debug_print(f"清理完成，共清理 {cleaned_count} 个目标")
    
    def check_required_files(self) -> bool:
        """检查必要文件是否存在"""
        required_files = [
            "main.py", 
            "config.json", 
            "global_settings.py"
        ]
        
        # 检查必要文件
        missing_required = []
        for file in required_files:
            if not os.path.exists(file):
                missing_required.append(file)
        
        if missing_required:
            raise FileNotFoundError(f"必要文件未找到: {', '.join(missing_required)}")
        
        # Linux 特定文件检查
        if self.system == "linux":
            linux_files = [
                self.config.get("desktop_file"),  # tms-controller.desktop
                "favicon.png"  # 检查 favicon.png
            ]
            
            for file in linux_files:
                if file and not os.path.exists(file):
                    debug_print(f"警告: Linux 特定文件未找到: {file}")
                else:
                    debug_print(f"找到 Linux 文件: {file}")
        
        # 检查协议配置目录
        protocol_configs_dir = "protocol_configs"
        if not os.path.exists(protocol_configs_dir):
            debug_print(f"警告: 协议配置目录未找到: {protocol_configs_dir}")
        else:
            try:
                json_files = [f for f in os.listdir(protocol_configs_dir) if f.endswith('.json')]
                if not json_files:
                    debug_print(f"警告: 在 {protocol_configs_dir} 目录中未找到任何JSON文件")
                else:
                    debug_print(f"找到协议配置文件: {json_files[:3]}{'...' if len(json_files) > 3 else ''}")
            except Exception as e:
                debug_print(f"检查协议配置目录时出错: {e}")
        
        debug_print("文件检查完成")
        return True
    
    def setup_environment(self) -> None:
        """设置构建环境"""
        # 创建必要的目录
        os.makedirs(self.build_dir, exist_ok=True)
        os.makedirs(self.temp_dir, exist_ok=True)
        
        # 检查并安装 Nuitka
        self.install_nuitka()
        
        # Linux 特定设置
        if self.system == "linux":
            # 检查并安装 patchelf
            self.install_patchelf()
            self.prepare_linux_desktop_files()

    def install_patchelf(self) -> None:
        """检查并安装 patchelf"""
        try:
            # 检查 patchelf 是否安装
            subprocess.run(["patchelf", "--version"], capture_output=True, check=True)
            debug_print("patchelf 已安装")
        except (subprocess.CalledProcessError, FileNotFoundError):
            debug_print("patchelf 未安装，正在安装...")
            try:
                # 尝试使用 apt 安装
                subprocess.run(["sudo", "apt", "update"], check=True)
                subprocess.run(["sudo", "apt", "install", "-y", "patchelf"], check=True)
                debug_print("patchelf 安装成功")
            except subprocess.CalledProcessError:
                raise RuntimeError("安装 patchelf 失败，请手动安装后重试")
    
    def install_nuitka(self) -> None:
        """检查并安装 Nuitka"""
        try:
            result = subprocess.run([
                sys.executable, "-m", "nuitka", "--version"
            ], capture_output=True, text=True, check=True, timeout=30)
            
            self.nuitka_version = result.stdout.strip()
            debug_print(f"Nuitka 已安装: {self.nuitka_version}")
            
        except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
            debug_print("Nuitka 未安装，正在安装...")
            try:
                subprocess.run([
                    sys.executable, "-m", "pip", "install", "nuitka"
                ], check=True, timeout=300)
                debug_print("Nuitka 安装成功")
                
                # 重新检查版本
                result = subprocess.run([
                    sys.executable, "-m", "nuitka", "--version"
                ], capture_output=True, text=True, check=True)
                self.nuitka_version = result.stdout.strip()
                
            except subprocess.CalledProcessError as e:
                raise RuntimeError(f"安装 Nuitka 失败: {e}")
    
    def prepare_linux_desktop_files(self) -> None:
        """准备 Linux 桌面文件和图标"""
        if self.system != "linux":
            return
            
        debug_print("准备 Linux 桌面文件和图标...")
        
        # 处理桌面文件
        desktop_file = self.config.get("desktop_file")
        if desktop_file and os.path.exists(desktop_file):
            # 复制桌面文件到构建目录
            shutil.copy2(desktop_file, os.path.join(self.build_dir, desktop_file))
            debug_print(f"已复制桌面文件: {desktop_file}")
            
            # 验证桌面文件内容
            self.validate_desktop_file(desktop_file)
        else:
            debug_print("错误: 未找到桌面文件，无法创建 Linux 应用")
            raise FileNotFoundError(f"桌面文件未找到: {desktop_file}")
        
        # 处理图标文件
        icon_source = "favicon.png"  # 使用现有的 favicon.png
        install_icon_name = self.config.get("install_icon_name", "tms-controller.png")
        
        if os.path.exists(icon_source):
            # 复制并重命名图标文件
            target_icon_path = os.path.join(self.build_dir, install_icon_name)
            shutil.copy2(icon_source, target_icon_path)
            debug_print(f"已准备应用程序图标: {install_icon_name} (从 {icon_source})")
            
            # 验证图标文件
            self.validate_icon_file(target_icon_path)
        else:
            debug_print("警告: 未找到图标文件 favicon.png")
    
    def validate_desktop_file(self, desktop_file: str) -> None:
        """验证桌面文件内容"""
        try:
            with open(desktop_file, 'r') as f:
                content = f.read()
            
            # 检查必要的字段
            required_fields = ["Name=", "Exec=", "Icon="]
            missing_fields = []
            
            for field in required_fields:
                if field not in content:
                    missing_fields.append(field)
            
            if missing_fields:
                debug_print(f"警告: 桌面文件缺少字段: {missing_fields}")
            else:
                debug_print("桌面文件验证通过")
                
        except Exception as e:
            debug_print(f"验证桌面文件时出错: {e}")
    
    def validate_icon_file(self, icon_path: str) -> None:
        """验证图标文件"""
        try:
            # 检查文件大小
            file_size = os.path.getsize(icon_path)
            if file_size == 0:
                debug_print("警告: 图标文件大小为 0")
            else:
                debug_print(f"图标文件大小: {file_size} 字节")
                
        except Exception as e:
            debug_print(f"验证图标文件时出错: {e}")
    
    def build_nuitka_command(self) -> List[str]:
        """构建 Nuitka 命令"""
        cmd = [
            sys.executable, "-m", "nuitka",
            "--standalone",
            "--onefile",
            self.config["console_option"],
            f"--output-dir={self.temp_dir}",
            f"--output-filename={self.app_name}",
            "--remove-output",
            "--assume-yes-for-downloads",
        ]

        # 平台特定图标选项
        if self.system == "linux":
            # 使用 favicon.png 作为 Linux 图标
            if os.path.exists("favicon.png"):
                cmd.append("--linux-icon=favicon.png")
                debug_print("使用 favicon.png 作为 Linux 图标")
        else:
            icon_file = self.config.get("icon_file")
            if icon_file and os.path.exists(icon_file):
                cmd.append(self.config["icon_option"])
                debug_print(f"使用图标文件: {icon_file}")
        
        # Windows 特定选项
        if self.system == "windows":
            cmd.extend([
                f"--windows-company-name={self.config['company_name']}",
                f"--windows-product-name={self.app_name}",
                f"--windows-file-version={APP_VERSION}",
                f"--windows-file-description={self.config['file_description']}",
            ])
        
        # macOS 特定选项
        elif self.system == "darwin":
            cmd.extend([
                f"--macos-app-name={self.app_name}",
                f"--macos-app-version={APP_VERSION}",
            ])
        
        # 优化选项
        cpu_count = os.cpu_count() or 4
        cmd.extend([
            "--lto=yes",
            f"--jobs={min(cpu_count, 8)}",
            "--quiet",
        ])
        
        # 数据文件包含
        data_files = [
            ("config.json", "config.json"),
            ("global_settings.py", "global_settings.py"),
        ]
        
        for src, dest in data_files:
            if os.path.exists(src):
                cmd.append(f"--include-data-files={src}={dest}")
        
        # 图标文件包含 - 包含所有可能的图标文件
        icon_files = ["favicon.ico", "favicon.png", "favicon.png", "favicon.icns"]
        for icon in icon_files:
            if os.path.exists(icon):
                cmd.append(f"--include-data-files={icon}={icon}")
        
        # 包含协议配置目录
        if os.path.exists("protocol_configs"):
            cmd.append("--include-data-dir=protocol_configs=protocol_configs")
        
        # 模块包含
        modules = self.get_required_modules()
        for module in modules:
            cmd.append(f"--include-module={module}")
        
        # 启用插件
        plugins = ["tk-inter"]
        for plugin in plugins:
            cmd.append(f"--plugin-enable={plugin}")
        
        # 添加主脚本
        cmd.append("main.py")
        
        debug_print(f"构建命令包含 {len(cmd)} 个参数")
        return cmd
    
    def get_required_modules(self) -> List[str]:
        """获取需要包含的模块列表"""
        base_modules = [
            'json', 'time', 'threading', 'logging', 'collections', 
            'queue', 'pathlib', 'os', 'sys', 'datetime'
        ]
        
        # 动态检测项目特定模块
        project_modules = []
        possible_modules = [
            'liquid_cooler_ui', 'serial_manager', 'protocol_parser',
            'data_logger', 'config_manager', 'serial_worker', 'serial'
        ]
        
        for module in possible_modules:
            if self.check_module_exists(module):
                project_modules.append(module)
        
        return base_modules + project_modules
    
    def check_module_exists(self, module_name: str) -> bool:
        """检查模块是否存在"""
        try:
            if module_name == "serial":
                import serial
                return True
            else:
                # 检查文件是否存在
                if os.path.exists(f"{module_name}.py"):
                    return True
        except ImportError:
            pass
        
        return False
    
    def run_nuitka_build(self) -> None:
        """执行 Nuitka 打包"""
        debug_print("开始 Nuitka 打包...")
        debug_print(f"目标平台: {self.system} {self.arch}")
        
        cmd = self.build_nuitka_command()
        
        # 显示命令摘要
        debug_print(f"执行命令: {' '.join(cmd[:8])}...")
        
        try:
            # 设置超时（2小时）
            timeout = 7200
            
            # 执行打包
            result = subprocess.run(
                cmd, 
                check=True, 
                capture_output=True, 
                text=True,
                timeout=timeout
            )
            
            # 处理构建结果
            self.process_build_result()
            
        except subprocess.CalledProcessError as e:
            debug_print("❌ Nuitka 打包失败!")
            if e.stderr:
                debug_print(f"错误输出: {e.stderr[:1000]}...")
            if e.stdout:
                debug_print(f"标准输出: {e.stdout[:500]}...")
            raise RuntimeError(f"打包过程失败: {e}")
        except subprocess.TimeoutExpired:
            debug_print("⏰ 打包超时（2小时）")
            raise RuntimeError("打包过程超时")
    
    def process_build_result(self) -> None:
        """处理构建结果"""
        debug_print("处理构建结果...")
        
        # 预期的输出文件路径
        possible_paths = [
            os.path.join(self.temp_dir, f"{self.app_name}{self.config['exe_suffix']}"),
            os.path.join(self.temp_dir, f"{self.app_name}.dist", f"{self.app_name}{self.config['exe_suffix']}"),
        ]
        
        built_file = None
        for file_path in possible_paths:
            if os.path.exists(file_path):
                built_file = file_path
                debug_print(f"找到构建文件: {built_file}")
                break
        
        # 深度搜索
        if not built_file:
            debug_print("在标准位置未找到文件，开始深度搜索...")
            for root, dirs, files in os.walk(self.temp_dir):
                for file in files:
                    if self.app_name in file and not file.endswith('.py'):
                        candidate = os.path.join(root, file)
                        built_file = candidate
                        debug_print(f"深度搜索找到文件: {built_file}")
                        break
                if built_file:
                    break
        
        if built_file and os.path.exists(built_file):
            # 计算文件大小
            if os.path.isdir(built_file):
                file_size = sum(f.stat().st_size for f in Path(built_file).rglob('*') if f.is_file()) / (1024 * 1024)
            else:
                file_size = os.path.getsize(built_file) / (1024 * 1024)
            
            # 移动到最终目录
            final_name = f"{self.app_name}{self.config['exe_suffix']}"
            final_path = os.path.join(self.build_dir, final_name)
            
            # 确保目标目录存在
            os.makedirs(self.build_dir, exist_ok=True)
            
            # 如果目标已存在，先删除
            if os.path.exists(final_path):
                if os.path.isdir(final_path):
                    shutil.rmtree(final_path)
                else:
                    os.remove(final_path)
            
            # 移动文件
            if os.path.isdir(built_file):
                shutil.copytree(built_file, final_path)
            else:
                shutil.copy2(built_file, final_path)
            
            debug_print("✅ 打包完成!")
            debug_print(f"📁 生成文件: {final_path}")
            debug_print(f"📊 文件大小: {file_size:.2f} MB")
            debug_print(f"🖥️  目标平台: {self.system}/{self.arch}")
            
            # 显示构建信息
            self.show_build_info(final_path)
        else:
            debug_print("❌ 错误: 未找到生成的可执行文件")
            debug_print("请在以下位置查找:")
            for root, dirs, files in os.walk(self.temp_dir):
                for file in files:
                    if self.app_name in file:
                        debug_print(f"  - {os.path.join(root, file)}")
            raise RuntimeError("未找到构建输出文件")
    
    def show_build_info(self, file_path: str) -> None:
        """显示构建信息"""
        info = f"""
        🎉 构建成功!
        ========================
        应用名称: {self.app_name}
        版本: {APP_VERSION}
        平台: {self.system} {self.arch}
        Nuitka: {self.nuitka_version or 'Unknown'}
        构建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
        输出路径: {file_path}
        ========================
        """
        debug_print(info)
    
    def create_distribution_package(self) -> None:
        """创建分发包"""
        if not os.path.exists(self.build_dir):
            return
        
        debug_print("创建分发包...")
        
        # 查找构建的文件
        build_items = []
        for item in os.listdir(self.build_dir):
            if self.app_name in item or item.endswith('.desktop') or item == 'tms-controller.png':
                build_items.append(item)
        
        if not build_items:
            debug_print("未找到构建文件，跳过创建分发包")
            return
        
        package_name = f"{self.app_name}_{self.system}_{self.arch}"
        
        try:
            if self.system == "windows":
                archive_path = os.path.join(self.build_dir, package_name)
                shutil.make_archive(archive_path, 'zip', self.build_dir)
                debug_print(f"📦 已创建 ZIP 分发包: {package_name}.zip")
            else:
                archive_path = os.path.join(self.build_dir, package_name)
                shutil.make_archive(archive_path, 'gztar', self.build_dir)
                debug_print(f"📦 已创建 TAR.GZ 分发包: {package_name}.tar.gz")
                
            # 显示分发包内容
            debug_print("分发包包含的文件:")
            for item in build_items:
                debug_print(f"  - {item}")
                
        except Exception as e:
            debug_print(f"创建分发包时出错: {e}")
    
    def create_install_script(self) -> None:
        """创建 Linux 安装脚本"""
        if self.system != "linux":
            return
            
        debug_print("创建 Linux 安装脚本...")
        
        install_script = f"""#!/bin/bash
# TMS Controller Linux 安装脚本
# 版本: {APP_VERSION}

APP_NAME="tms-controller"
APP_VERSION="{APP_VERSION}"
BIN_DIR="/usr/local/bin"
DESKTOP_DIR="$HOME/.local/share/applications"
ICON_DIR="$HOME/.local/share/icons"

echo "安装 TMS Controller {APP_VERSION}..."

# 检查是否以 root 运行
if [ "$EUID" -eq 0 ]; then
    BIN_DIR="/usr/bin"
    DESKTOP_DIR="/usr/share/applications"
    ICON_DIR="/usr/share/icons/hicolor/256x256/apps"
fi

# 安装文件
echo "复制应用程序文件..."
sudo install -Dmv755 $APP_NAME_v$APP_VERSION $BIN_DIR/$APP_NAME
sudo install -Dmv644 tms-controller.desktop -t $DESKTOP_DIR/
sudo install -Dmv644 tms-controller.png -t $ICON_DIR/

echo "安装完成!"
echo "应用程序已安装到: $INSTALL_DIR"
echo "桌面快捷方式: $DESKTOP_DIR/tms-controller.desktop"
echo "命令行命令: tms-controller"
"""
        
        script_path = os.path.join(self.build_dir, "install.sh")
        with open(script_path, 'w') as f:
            f.write(install_script)
        
        # 设置执行权限
        os.chmod(script_path, 0o755)
        debug_print(f"已创建安装脚本: install.sh")
    
    def build(self) -> None:
        """执行完整的构建流程"""
        start_time = datetime.now()
        debug_print("🚀 开始跨平台构建...")
        
        try:
            # 1. 清理旧构建
            self.clean_previous_builds()
            
            # 2. 检查必要文件
            self.check_required_files()
            
            # 3. 设置环境
            self.setup_environment()
            
            # 4. 执行构建
            self.run_nuitka_build()
            
            # 5. 创建分发包
            self.create_distribution_package()
            
            # 6. 创建 Linux 安装脚本
            if self.system == "linux":
                self.create_install_script()
            
            # 7. 最终清理
            if os.path.exists(self.temp_dir):
                shutil.rmtree(self.temp_dir)
            
            end_time = datetime.now()
            duration = (end_time - start_time).total_seconds()
            debug_print(f"✅ 构建过程完成! 耗时: {duration:.2f} 秒")
            
        except Exception as e:
            debug_print(f"❌ 构建过程中出现错误: {e}")
            # 保留临时文件用于调试
            if os.path.exists(self.temp_dir):
                debug_print(f"临时文件保留在: {self.temp_dir}")
            raise


def main() -> None:
    """主函数"""
    try:
        builder = NuitkaBuilder()
        builder.build()
    except KeyboardInterrupt:
        debug_print("⚠️  构建被用户中断")
        sys.exit(130)
    except Exception as e:
        debug_print(f"💥 构建失败: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()